﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using QTPlugin;
using QTPlugin.Interop;
using Kawanon.Usability;
using Kawanon.Usability.Visualization;
using Kawanon.Foundation;

namespace Kawanon.QtHoney
{
	public abstract class ViewBase : IDisposable
	{
		private const uint WM_USER = 0x0400;

		private const uint SB_SIMPLE = (WM_USER + 9);
		private const uint SB_SETTEXTW = (WM_USER + 11);

		private const uint SBT_NOBORDERS = 0x0100;

		[DllImport("user32.dll")]
		static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, bool wParam, IntPtr lParam);

		// SB_SETTEXTW
		[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, UInt32 wParam, string lParam);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool IsWindow(IntPtr hWnd);

		protected class PopupMenuStrip : ContextMenuStrip
		{
			public GestureProvider Provider { get; set; }

			protected override void WndProc(ref Message m)
			{
				if (Provider.ProcessMessage(ref m))
				{
					return;
				}
				base.WndProc(ref m);
			}
		}

		public IPluginServer Server { get; set; }
		public IPluginClient Plugin { get; set; }
		public IntPtr StatusBarHandle { get; set; }
		public IShellView ShellView { get; set; }
		public bool DrawTrails
		{
			get
			{
				return m_bDrawTrails;
			}
			set
			{
				//if (value)
				//{
				//    m_provider.Tick += GestureProvider_Tick;
				//}
				//else
				//{
				//    m_provider.Tick -= GestureProvider_Tick;
				//}
				m_bDrawTrails = value;
			}
		}
		public ITrailRenderer Renderer
		{
			get
			{
				return m_renderer;
			}
			set
			{
				if (this.Handle != IntPtr.Zero)
				{
					value.WindowHandle = this.Handle;
				}
				m_renderer = value;
			}
		}
		//public IEnumerable<GestureExpression> Expressions
		//{
		//    get
		//    {
		//        return m_provider.Expressions;
		//    }
		//    set
		//    {
		//        m_provider.Expressions = value;
		//    }
		//}
		//public GestureInformation GestureInformation
		//{
		//    get
		//    {
		//        return m_provider.GestureInformation;
		//    }
		//    set
		//    {
		//        m_provider.GestureInformation = value;
		//    }
		//}
		public GestureProvider Provider
		{
			get
			{
				return m_provider;
			}
			set
			{
				if (m_provider != null)
				{
					UnregisterProvider(m_provider);
				}
				if (value != null)
				{
					RegisterProvider(value);
				}

				m_provider = value;
			}
		}
		public IViewProxy View { get; set; }
		public IWindowAdapter Adapter
		{
			get
			{
				return m_adapter;
			}
			set
			{
				if (m_adapter != null)
				{
					m_adapter.WindowMessage -= new WindowMessageHandler(ProcessWindowMessage);
				}
				if (value != null)
				{
					value.WindowMessage += new WindowMessageHandler(ProcessWindowMessage);
				}

				m_adapter = value;
			}
		}
		public IntPtr Handle
		{
			get
			{
				return m_handle;
			}
			set
			{
				if (Renderer != null)
				{
					Renderer.WindowHandle = value;
				}
				m_handle = value;
			}
		}

		protected GestureProvider m_provider = null;
		private bool m_bDrawTrails = false;
		private PopupMenuStrip m_menuStrip = null;
		private WheelExpression m_wheelExpr = null;
		private WheelDirection m_firstDirection;
		private IWindowAdapter m_adapter = null;
		private IntPtr m_handle;
		private ITrailRenderer m_renderer;

		//public PopupMenuStrip MenuStrip
		//{
		//    get
		//    {
		//        return m_menuStrip;
		//    }
		//    set
		//    {
		//        m_menuStrip = value;
		//    }
		//}
		//public WheelExpression WheelExpression
		//{
		//    get
		//    {
		//        return m_wheelExpr;
		//    }
		//    set
		//    {
		//        m_wheelExpr = value;
		//    }
		//}

		public ViewBase()
		{
		}

		public void KeepGesture(ViewBase view)
		{
			m_menuStrip = view.m_menuStrip;
			m_wheelExpr = view.m_wheelExpr;
			m_firstDirection = view.m_firstDirection;
		}

		protected virtual void RegisterProvider(GestureProvider provider)
		{
			provider.ResolveTarget += new GestureEventHandler(GestureProvider_ResolveTarget);
			provider.GestureDetected += new GestureEventHandler(GestureProvider_GestureDetected);
			provider.TrailDetected += new GestureEventHandler(GestureProvider_TrailDetected);
			provider.GestureCancelled += new GestureEventHandler(GestureProvider_GestureCancelled);
			provider.Tick += GestureProvider_Tick;

			provider.BeforeWheelGesture += new WheelEventHandler(GestureProvider_BeforeWheelGesture);
			provider.WheelDetected += new WheelEventHandler(GestureProvider_WheelDetected);
			provider.AfterWheelGesture += new WheelEventHandler(GestureProvider_AfterWheelGesture);

			provider.AllowUnknownExpression = true;
		}

		protected virtual void UnregisterProvider(GestureProvider provider)
		{
			provider.ResolveTarget -= new GestureEventHandler(GestureProvider_ResolveTarget);
			provider.GestureDetected -= new GestureEventHandler(GestureProvider_GestureDetected);
			provider.TrailDetected -= new GestureEventHandler(GestureProvider_TrailDetected);
			provider.GestureCancelled -= new GestureEventHandler(GestureProvider_GestureCancelled);
			provider.Tick -= GestureProvider_Tick;

			provider.BeforeWheelGesture -= new WheelEventHandler(GestureProvider_BeforeWheelGesture);
			provider.WheelDetected -= new WheelEventHandler(GestureProvider_WheelDetected);
			provider.AfterWheelGesture -= new WheelEventHandler(GestureProvider_AfterWheelGesture);
		}

		protected virtual void ProcessWindowMessage(ref Message m)
		{
		}

		protected virtual object QueryTarget()
		{
			return null;
		}

		private ToolStripItem FindSelectedItem(ToolStripItemCollection c)
		{
			ToolStripItem[] items = new ToolStripItem[c.Count];
			c.CopyTo(items, 0);

			return Array.Find(items, item => item.Selected);
		}

		void GestureProvider_BeforeWheelGesture(object sender, WheelEventArgs e)
		{
			if (m_menuStrip != null)
			{
				Dbg.Echo("WheelGesture Duplication");
			}

			m_firstDirection = e.Direction;

			m_wheelExpr = m_provider.GestureInformation.WheelExpressions.Find(
				expr => (expr.Button == e.Button) && (expr.Direction & e.Direction) == e.Direction);
			if (m_wheelExpr == null)
			{
				return;
			}

			if (m_wheelExpr.Menu != PopupMenus.None)
			{
				PopupMenuFactory factory = new PopupMenuFactory();
				factory.Server = Server;
				m_menuStrip = new PopupMenuStrip();
				if (Server != null)
				{
					m_menuStrip.Renderer = Server.GetMenuRenderer();
				}
				m_menuStrip.Provider = m_provider;
				m_menuStrip.SuspendLayout();

				factory.CreateItems(m_menuStrip.Items, m_wheelExpr.Menu);

				m_menuStrip.ResumeLayout();

				if (m_menuStrip.Items.Count > 0)
				{
					m_menuStrip.Show(Cursor.Position);
				}
			}
			else if (m_wheelExpr.Action != null)
			{
				Execute(m_wheelExpr.Action, QueryTarget(), false);
			}
		}

		private void MoveSelection(ToolStripItemCollection items, int origin, bool up)
		{
			int index = origin;
			if (up)
			{
				index--;
				if (index < 0)
				{
					index = items.Count - 1;
				}
			}
			else
			{
				index++;
				if (index >= items.Count)
				{
					index = 0;
				}
			}
			if (items[index] is ToolStripSeparator)
			{
				MoveSelection(items, index, up);
			}
			else
			{
				items[index].Select();
			}
		}

		void GestureProvider_WheelDetected(object sender, WheelEventArgs e)
		{
			if (m_menuStrip != null)
			{
				ToolStripItemCollection items = m_menuStrip.Items;
				ToolStripItem item = FindSelectedItem(items);
				if (item == null)
				{
					if (items.Count > 0)
					{
						items[0].Select();
					}
					return;
				}
				int index = items.IndexOf(item);
				if (e.Direction == WheelDirection.Up)
				{
					MoveSelection(items, index, true);
				}
				else if (e.Direction == WheelDirection.Down)
				{
					MoveSelection(items, index, false);
				}
			}
			else if (m_wheelExpr.Action != null)
			{
				Execute(m_wheelExpr.Action, QueryTarget(), e.Direction != m_firstDirection);
			}
		}

		void GestureProvider_AfterWheelGesture(object sender, WheelEventArgs e)
		{
			if (m_menuStrip != null)
			{
				ToolStripItem item = FindSelectedItem(m_menuStrip.Items);
				if (item != null)
				{
					item.PerformClick();
				}

				m_menuStrip.Close();
				//m_menuStrip.Dispose();
				m_menuStrip = null;
			}

			m_wheelExpr = null;
		}

		/*
		protected override void OnHandleChange()
		{
			base.OnHandleChange();

			if (Renderer != null)
			{
				Renderer.WindowHandle = this.Handle;	// レンダラのハンドルも無効にする
			}
		}
		*/

		void GestureProvider_Tick(object sender, GestureProvider.NotifyEventArgs e)
		{
			if (DrawTrails)
			{
				Renderer.DrawLine(e.Previous, e.Current);
			}
		}

		protected virtual void OnEnterGesture()
		{
		}

		protected virtual void OnLeaveGesture(Action.Arguments ret)
		{
		}

		void GestureProvider_ResolveTarget(object sender, GestureEventArgs e)
		{
			if (DrawTrails)
			{
				Renderer.WindowHandle = this.Handle;
				Renderer.RenderingInformation.ChangeColor(e.Expression.Button);
				Renderer.SetOrigin(Cursor.Position);
			}

			OnEnterGesture();
		}

		void GestureProvider_GestureCancelled(object sender, GestureEventArgs e)
		{
			if (DrawTrails)
			{
				Renderer.Erase();
			}
		}

		void GestureProvider_TrailDetected(object sender, GestureEventArgs e)
		{
			if (Server != null)
			{
				//NOTE: ビューの外でボタンを離した際、ReBarなどのメニューを開かないようにする
				EnableWindow(Server.ExplorerHandle, false);
				//Dbg.Play();
			}

			if (StatusBarHandle != IntPtr.Zero)
			{
				List<GestureExpression> list = new List<GestureExpression>(e.Suggestions);
				GestureExpression equivalent = GestureSettings.FindExpression(list, e.Expression.Trails);
				if (equivalent != null)	// 候補の中に完全にマッチするものがあれば、先頭に表示する
				{
					list.Remove(equivalent);
					list.Insert(0, equivalent);
				}

				int length = e.Expression.Trails.Length;
				List<string> labels = list.ConvertAll(delegate(GestureExpression expr)
				{
					int[] trails = new int[expr.Trails.Length - length];
					Array.Copy(expr.Trails, length, trails, 0, trails.Length);
					string s = GestureExpression.FormatTrails(trails) + expr.Name;
					return s;
				});
				string text = String.Format("{0}  ({1})",
					e.Expression.FormatTrails(),
					(labels.Count > 0) ? String.Join(", ", labels.ToArray()) : "なし");

				SendMessage(StatusBarHandle, SB_SIMPLE, true, IntPtr.Zero);

				if (SendMessage(StatusBarHandle, SB_SETTEXTW, (255 | SBT_NOBORDERS), text) == IntPtr.Zero)
				{
					Dbg.Abort("SB_SETTEXTW Failed");
				}
			}
		}

		private Action.Arguments Execute(Action action, object target, bool reverse)
		{
			Action.Arguments args = new Action.Arguments();
			args.Server = Server;
			args.Plugin = Plugin;
			args.ShellView = ShellView;
			args.ViewHandle = this.Handle;
			args.Target = target;
			args.Reverse = reverse;
			args.KeepSelection = false;

			if (Server != null)
			{
				try
				{
					action.Execute(args);
				}
				catch (NotImplementedException)
				{
				}
			}
			else
			{
				try
				{
					// とりあえず試行してみる
					action.Execute(args);
				}
				catch (NotImplementedException)
				{
				}
				catch (Exception)
				{
					//MessageBox.Show(this, String.Format("{0} {1} {2}", expr.Name, expr.FormatTrails(), ex.Message));
				}
			}
			return args;
		}

		void GestureProvider_GestureDetected(object sender, GestureEventArgs e)
		{
			if (Server != null)
			{
				EnableWindow(Server.ExplorerHandle, true);
				//Dbg.Play();
			}

			// 軌跡を消去
			if (DrawTrails)
			{
				Renderer.Erase();
			}

			Action.Arguments ret = Execute(e.Expression.Action, QueryTarget(), false);
			if (!IsWindow(this.Handle))
			{
				// ウィンドウが閉じられた場合は切り上げ
				return;
			}
			OnLeaveGesture(ret);

			if (StatusBarHandle != IntPtr.Zero)
			{
				SendMessage(StatusBarHandle, SB_SIMPLE, false, IntPtr.Zero);
			}
		}

		public virtual void Dispose()
		{
			if (Handle == IntPtr.Zero)
			{
				return;
			}

			if (Adapter != null)
			{
				Adapter.Dispose();
			}
			UnregisterProvider(m_provider);

			Handle = IntPtr.Zero;
		}
	}

	public class ViewForWebBrowser : ViewBase
	{
		private const uint WM_SETFOCUS = 0x0007;
		private const uint WM_SHOWWINDOW = 0x0018;
		private const uint WM_PAINT = 0x000F;
		private const uint WM_LBUTTONDOWN = 0x0201;
		private const uint WM_LBUTTONUP = 0x0202;
		private const uint WM_RBUTTONDOWN = 0x0204;
		private const uint WM_RBUTTONUP = 0x0205;
		private const uint WM_DESTROY = 0x0002;

		protected override void ProcessWindowMessage(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				case WM_RBUTTONDOWN:
					break;

				case WM_DESTROY:
					Dispose();
					break;
			}

			m_provider.ProcessMessage(ref m);

			// 常にDefWndProcで処理
		}
	}

	public class ViewForFolderView : ViewBase
	{
		private const uint WM_CONTEXTMENU = 0x007B;
		private const uint WM_LBUTTONDOWN = 0x0201;
		private const uint WM_LBUTTONUP = 0x0202;
		private const uint WM_RBUTTONDOWN = 0x0204;
		private const uint WM_RBUTTONUP = 0x0205;
		private const uint WM_MOUSEMOVE = 0x0200;
		private const uint WM_CAPTURECHANGED = 0x0215;
		private const uint WM_TIMER = 0x0113;
		private const uint WM_CLOSE = 0x0010;
		private const uint WM_DESTROY = 0x0002;
		private const uint WM_USER = 0x0400;

		private const uint S_OK = 0;

		private const uint SVGIO_BACKGROUND = 0;
		private const uint SVGIO_SELECTION = 0x1;
		private const uint SVGIO_ALLVIEW = 0x2;
		private const uint SVGIO_CHECKED = 0x3;
		private const uint SVGIO_TYPE_MASK = 0xf;
		private const uint SVGIO_FLAG_VIEWORDER = 0x80000000;

		[DllImport("user32.dll")]
		static extern IntPtr SetCapture(IntPtr hWnd);

		[DllImport("user32.dll")]
		static extern IntPtr GetCapture();

		[DllImport("user32.dll")]
		static extern bool ReleaseCapture();

		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
		static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

		private int m_nOriginItem;

		public ViewForFolderView()
		{
		}

		protected override void RegisterProvider(GestureProvider provider)
		{
			provider.RightClick += new MouseEventHandler(GestureProvider_RightClick);

			base.RegisterProvider(provider);
		}

		protected override void UnregisterProvider(GestureProvider provider)
		{
			provider.RightClick -= new MouseEventHandler(GestureProvider_RightClick);

			base.UnregisterProvider(provider);
		}

		protected override void OnEnterGesture()
		{
			m_nOriginItem = View.HitTest(Cursor.Position, true);
			if (m_nOriginItem != -1)
			{
				//NOTE: 縮小ビューで効かないときがある？
				//Dbg.Play();
				View.SelectItem(m_nOriginItem, true);
				View.FocusItem(m_nOriginItem);
			}
		}

		protected override void OnLeaveGesture(Action.Arguments ret)
		{
			if (!ret.KeepSelection && m_nOriginItem != -1)
			{
				//NOTE: 選択した開始点のアイテムを戻す
				try
				{
					View.SelectItem(m_nOriginItem, false);
				}
				catch (OperationFailedException ex)
				{
					//NOTE: Vistaの場合、ウィンドウが破棄されてなくても
					//「更新」などビューが切り替わる場合に FALSE が返る
					Dbg.Abort("Test: " + ex.Message);
				}
			}
		}

		private IntPtr PointToDword(Point pt)
		{
			return (IntPtr)((pt.X & 0xffff) | ((pt.Y & 0xffff) << 16));
		}

		// 右クリックをエミュレート
		void GestureProvider_RightClick(object sender, MouseEventArgs e)
		{
			// 最初にすべての選択を解除する
			View.ClearSelection();

			//int count = (int)SendMessage(this.Handle, LVM_GETSELECTEDCOUNT, IntPtr.Zero, IntPtr.Zero);
			//if (count != 0)
			//{
			//    return;
			//}
			int iItem = View.HitTest(e.Location, true);
			if (iItem != -1)
			{
				View.SelectItem(iItem, true);
				View.FocusItem(iItem);
			}

			Application.DoEvents();	// 選択をすぐに切り替える（見かけ上もっさりしないように）

			//Message m = Message.Create(this.Handle, (int)WM_CONTEXTMENU, this.Handle, PointToDword(e.Location));
			//base.WndProc(ref m);
			IWindowAdapter adapter = this.Adapter;
			this.Adapter = null;
			SendMessage(this.Handle, WM_CONTEXTMENU, this.Handle, PointToDword(e.Location));
			this.Adapter = adapter;
		}

		protected override object QueryTarget()
		{
			if (ShellView == null)
			{
				Dbg.Abort("ShellView == null");
				return null;
			}

			Guid IID_IDataObject = new Guid("{0000010e-0000-0000-C000-000000000046}");
			object pv;
			if (ShellView.GetItemObject(SVGIO_SELECTION, ref IID_IDataObject, out pv) != S_OK)
			{
				return null;
			}
			return pv;
		}

		private bool IsItemDragStarted()
		{
			int count = View.GetSelectedCount();
			if (count == 0)
			{
				return false;
			}

			if (!View.HotItemIsSelected())
			{
				return false;
			}

			return true;
		}

		protected override void ProcessWindowMessage(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				case WM_RBUTTONDOWN:
					if (!Provider.GestureInformation.PreferGestureTrack &&
						IsItemDragStarted())
					{
						return;
					}
					break;

				//case WM_TIMER:
				//    GestureTimerProc(this.Handle, 0, IntPtr.Zero, 0);
				//    break;
				case WM_DESTROY:
					//DebugHelpers.Play();
					//Console.WriteLine("Destroy Window");

					Dispose();
					break;
			}

			if (m_provider.ProcessMessage(ref m))
			{
				throw new WindowMessageCancelledException();
			}
		}
	}
}
